#pragma once

#include <dae.h>
#include <dom.h>
#include <dom/domCOLLADA.h>
#include "ColladaLibs.h"
#include <string>
#include <vector>
using namespace std;

#include "..\\..\\Utility\\D3D\\D3DUtilityFunction.h"
#include "..\\MeshRender.h"
#include "StaticMesh.h"
#include "MorphAnimationKey.h"

template <class CBaseVertType, class CTargetVertType>
class MorphingMesh : public CMeshRender
{
public:
#pragma region 
	//COLLADA members
	//Set it so COLLADALoader can access privates
	//friend class COLLADALoader;

	//Bubble sort Animation array by least-most time
	void sortAnimation()
	{	
		for(unsigned int i = 0; i < Animations.size(); i++)	{
			for(unsigned int j = 0; j < i; j++)	{
				if(Animations[i].Time < Animations[j].Time)	{
					MorphAnimationKey temp = Animations[i];
					Animations[i] = Animations[j];
					Animations[j] = temp;
				}
			}
		}
	}
#pragma endregion
	//Accumulated time for the animation
	float accumulatedTime;
	//Target Vertex Buffers
	vector<ID3D11Buffer*> targetVertexBuffers;
	//Stride size for Target Vertices --Fixed number currently!!!
	static unsigned int TARGET_STRIDE_SIZE;

public:
	//Name
	string Name;
	//Base mesh
	StaticMesh<CBaseVertType>* Base;
	//Morph Targets
	vector<StaticMesh<CTargetVertType>*> Targets;
	//Animation
	vector<MorphAnimationKey> Animations;
	//Morph Weights
	float Weights[4];
	//Target indices
	short targetIndices[4];
	//Constructor
	//Constructor
	MorphingMesh(string Name)
	{
		//Set Name
		this->Name = Name;
		//Set Base to NULL
		Base = NULL;
		//Initialize Targets
		Targets = vector<StaticMesh<CTargetVertType>*>();
		
		//Initialize Animation
		Animations = vector<MorphAnimationKey>();
		//Initialize targetVertexBuffers array
		targetVertexBuffers = vector<ID3D11Buffer*>();
		//Set accumulatedTime to 0
		accumulatedTime = 0;
		//Set each of the targetIndices to -1 and Weights to 0
		for(unsigned int i = 0; i < 4; i++) {
			targetIndices[i] = -1;
			Weights[i] = 0;
		}
	}

	//Destructor
	virtual ~MorphingMesh()
	{
		//Release targetVertexBuffers
		while(!targetVertexBuffers.empty())
		{
			SAFE_RELEASE(targetVertexBuffers.back()); 
			targetVertexBuffers.pop_back();
		}

		//Delete Targets
		while(!Targets.empty())
		{
			SAFE_DELETE(Targets.back()); 
			Targets.pop_back();
		}

		//Delete Base Mesh
		if(Base)
			SAFE_DELETE(Base);
	}

	//OnDeviceLost
	virtual void OnD3D11DestroyDevice( void* pUserContext )
	{
		//Release targetVertexBuffers
		while(!targetVertexBuffers.empty())
		{
			SAFE_RELEASE(targetVertexBuffers.back()); 
			targetVertexBuffers.pop_back();
		}
		//Handle Targets
		for(unsigned int i = 0; i < Targets.size(); i++) 
			Targets[i]->OnD3D11DestroyDevice(NULL);
		//Handle Base
		Base->OnD3D11DestroyDevice(NULL);
	}
	//OnDeviceReset
	virtual HRESULT OnD3D11CreateDevice( ID3D11Device* pd3dDevice, ID3DX11EffectTechnique* pTech,
		const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, CDirectoryUtil* pDirUtil, void* pUserContext)
	{
		HRESULT hr = S_OK;
		//Create vertex and index buffer for the base
		hr = Base->OnD3D11CreateDevice(pd3dDevice,pTech,pBackBufferSurfaceDesc,pDirUtil,NULL);

		//Create vertex buffer for each target
		for(unsigned int i = 0; i < Targets.size(); i++) {
			//Make full VertexBuffer for it
			Targets[i]->OnD3D11CreateDevice(pd3dDevice,pTech,pBackBufferSurfaceDesc,pDirUtil,NULL);
			//First make an array of just the two components needed
			vector<VertexNorm> targetVertices = vector<VertexNorm>();

			for(unsigned int z = 0; z < Targets[i]->Vertices.size(); z++) 
			{  
				VertexNorm* pVert = static_cast<VertexNorm*>(&(Targets[i]->Vertices[z]));
				targetVertices.push_back(VertexNorm(pVert->Position, pVert->Normal));
			}

#pragma region //Then make the vertex buffer
			ID3D11Buffer* vertexBuffer = NULL;
			//Create Vertex Buffer
			V_RETURN(CD3DUtilityFunction::CreateVertexBuffer(pd3dDevice,TARGET_STRIDE_SIZE,(float*)(targetVertices.data()),targetVertices.size(),&vertexBuffer))
#pragma endregion
				//Push back
				targetVertexBuffers.push_back(vertexBuffer);
		}

		//create vertex layout
		hr = CD3DUtilityFunction::CreateVertexLayout(pd3dDevice,VertexNormUVTang_Morph_Layout,ARRAYSIZE(VertexNormUVTang_Morph_Layout),pTech,&m_pVertexLayout);
		return hr;
	}
	//Update with delta time
	void Update(float dt)
	{
		if(Animations.size() < 1) return;
		//Add to the accumulatedTime
		accumulatedTime += dt;
		//If the accumulatedTime is greater than the end time set it back to the start
		if(accumulatedTime > Animations.back().Time) accumulatedTime = 0;
		//Linear Animate
		LinearAnimate();
	}
	//Render
	void Render(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, ID3DX11Effect* pEffect, 
		ID3DX11EffectTechnique* pRenderTech, enum RENDERMESHTECH iMeshType, const D3DXMATRIX& mWorldCamera, 
		double fTime, float fElapsedTime, void* pUserContext)
	{
		if (iMeshType != RENDERMESHTECH::RENDERMORPHMESH)
		{
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
				return;
		}

		HRESULT hr = S_OK;
		//Set model world matrix
		D3DXMATRIX mWorldMatrix = Base->GetWorldMatrix() * mWorldCamera;
		hr = (pEffect->GetVariableByName("mWorld")->AsMatrix()->SetMatrix((float*)(&mWorldMatrix)));
		//hr = (pEffect->GetVariableByName("World")->AsMatrix())->SetMatrix((float*)(&(Base->World)));
		hr = (pEffect->GetVariableByName("Weight")->AsScalar())->SetFloatArray(&Weights[0],0, 4);
		// Set render resources
		pd3dImmediateContext->IASetInputLayout( m_pVertexLayout );
		ID3D11Buffer* pVB[5]; 
		pVB[0] = m_pVB;
		//Set Target vertexBuffer's
		for(unsigned int i = 0; i < 4; i++)	{
			if((Weights[i] <= 0) || (targetIndices[i] < 0)) 
				pVB[i] = NULL;
			else
				pVB[i] = targetVertexBuffers[targetIndices[i]];
		}
		UINT pStride[5] = {Base->GetStrideSize(), TARGET_STRIDE_SIZE,TARGET_STRIDE_SIZE,TARGET_STRIDE_SIZE,TARGET_STRIDE_SIZE};
		UINT pOffset[5] = { 0,0,0,0,0};
		pd3dImmediateContext->IASetVertexBuffers( 0, 5, pVB, pStride, pOffset );
		pd3dImmediateContext->IASetIndexBuffer(Base->GetIB(), DXGI_FORMAT_R32_UINT, 0);
		pd3dImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		// Render objects here...
		// Apply the technique contained in the effect 
		D3DX11_TECHNIQUE_DESC Desc;
		pRenderTech->GetDesc( &Desc );

		for( UINT iPass = 0; iPass < Desc.Passes; iPass++ )
		{
			pRenderTech->GetPassByIndex( iPass )->Apply( 0, pd3dImmediateContext );
			pd3dImmediateContext->DrawIndexed( Base->Indices.size(), 0, 0 );			
		}
		//Base->Render(pd3dDevice,pd3dImmediateContext,pEffect,pRenderTech,pViewCamera,pDirUtil,fTime,fElapsedTime,pUserContext);
	}
	//Draw it's targets as they are
	void RenderPeripheral(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, ID3DX11Effect* pEffect, ID3DX11EffectTechnique* pRenderTech,
		enum RENDERMESHTECH iMeshType, const D3DXMATRIX& mWorldCamera, double fTime, float fElapsedTime, void* pUserContext)
	{
		if (iMeshType != RENDERMESHTECH::RENDERSTATICMESH)
		{
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
				return;
		}

		//For each target...
		for(unsigned int i = 0; i < Targets.size(); i++)
		{	//Draw
			Targets[i]->Render(pd3dDevice,pd3dImmediateContext,pEffect, pRenderTech,iMeshType, mWorldCamera, fTime,fElapsedTime,pUserContext);
		}
	}
private:
	//Step Animation
	void StepAnimate()
	{
		//Weights for each target
		float* weightsList = new float[Targets.size()];
		//Set Weights and targetIndices to default values
		for(unsigned int i = 0; i < 4; i++) {
			targetIndices[i] = -1;
			Weights[i] = 0;
		}
		//Set weightsList to default values
		for(unsigned int i = 0; i < Targets.size(); i++) weightsList[i] = 0;

		//Get the value for each target for this frame
		for(unsigned int i = 0; i < Animations.size(); i++)	{
			//Check if this keyframe applies to this time
			if(Animations[i].Time > accumulatedTime) break;
			//Set the weight for the target being referenced
			weightsList[Animations[i].Target] = Animations[i].Weight;
		}
		//Set the Weights and targetIndices for this frame
		short currentIndex = 0;
		for(unsigned int i = 0; i < Targets.size(); i++) {
			//Check if there is some contribution by this target
			if(weightsList[i] > 0)	{
				//Set the weight at the currentIndex
				Weights[currentIndex] = weightsList[i];				
				//Set the targetIndex at the currentIndex
				targetIndices[currentIndex] = (short)(i);				
				//Index to next to fill
				currentIndex++;
			}
			//Check if no more targets can be added
			if(currentIndex > 3) break;
		}
		//Dereference pointers
		delete [] weightsList;
	}

	//Linear Animation
	void LinearAnimate()
	{
		//Weights for each target
		float* weightsList = new float[Targets.size()];
		//Keyframe for this frame
		short* KI1 = new short[Targets.size()];
		//Keyframe for the next frame
		short* KI2 = new short[Targets.size()];
		//Keep track of which targets next keyframe has been found
		bool* KB = new bool[Targets.size()];
		//Keep track of where to search for the next keyframes
		unsigned int nextSearchStart = 0;
		//Set Weights and targetIndices to default values
		for(unsigned int i = 0; i < 4; i++)	{
			targetIndices[i] = -1;
			Weights[i] = 0;
		}
		//Set weightsList, KI1, KI2 and KB to default values
		for(unsigned int i = 0; i < Targets.size(); i++) {
			weightsList[i] = 0;
			KI1[i] = -1;
			KI2[i] = -1;
			KB[i] = false;
		}
		//Get the current keyframes
		for(unsigned int i = 0; i < Animations.size(); i++)	{
			//Set nextSearch
			nextSearchStart = i;
			//Check if this keyframe applies to this time
			if(Animations[i].Time > accumulatedTime) break;
			//Set the keyframe index for this target at this frame
			KI1[Animations[i].Target] = i;
		}
		//Get the next keyframes
		unsigned int targetTrack = 0;
		for(unsigned int i = nextSearchStart; i < Animations.size(); i++)	{
			//If no next keyframe has been found yet for this Target
			if(!KB[Animations[i].Target]) {
				//Set the next keyframe index for this target
				KI2[Animations[i].Target] = i;
				//Set the bool for this to true
				KB[Animations[i].Target] = true;
				//Add to the target complete count
				targetTrack++;
			}
			//If all the Targets next frames have been found, break
			if(targetTrack > Targets.size()) break;
		}

		//Linear interpolation of each Targets weight
		for(unsigned int i = 0; i < Targets.size(); i++) {
			//Get the values
			float a = (KI1[i] >= 0) ? Animations[KI1[i]].Weight : 0;
			float b = (KI2[i] >= 0) ? Animations[KI2[i]].Weight : 0;
			//Get the times
			float T1 = (KI1[i] >= 0) ? Animations[KI1[i]].Time : Animations.front().Time;
			float T2 = (KI2[i] >= 0) ? Animations[KI2[i]].Time : Animations.back().Time;
			//Lerp
			if(T1 == T2)
			{
				weightsList[i] = a;
			}
			else
			{
				//Interpolation modifier
				float s = (accumulatedTime - T1) / (T2 - T1);

				weightsList[i] = Lerp(a, b, s);
			}
		}

		//Set the weights and targetIndices for this frame
		short currentIndex = 0;
		for(unsigned int i = 0; i < Targets.size(); i++)
		{
			//Check if there is some contribution by this target
			if(weightsList[i] > 0)
			{
				//Set the weight at the currentIndex
				Weights[currentIndex] = weightsList[i];

				//Set the targetIndex at the currentIndex
				targetIndices[currentIndex] = i;

				//Index to next to fill
				currentIndex++;
			}

			//Check if no more targets can be added
			if(currentIndex > 3) break;
		}

		//Dereference pointers
		delete [] weightsList;
		delete [] KI1;
		delete [] KI2;
		delete [] KB;
	}

	//Lerp function
	float Lerp(float a, float b, float s)
	{
		return (a + ((b - a) * s));
	}

	//Bezier Animation
	void BezierAnimate()
	{
	}

	//Hermite Animation
	void HermiteAnimate()
	{
	}
};

template<class CVertType, class CTargetVertType> 
unsigned int MorphingMesh<CVertType, CTargetVertType>::TARGET_STRIDE_SIZE = CTargetVertType::STRIDE_SIZE;